#include "opencv_camera.h"
#include <QVector>
#include <QImage>
#include <fstream>
#include <QDebug>

opencv_camera::opencv_camera(QObject *parent)
    : QObject{parent}
{

}

bool opencv_camera::openCameraDev(int devID)
{
    this->camera.open(devID);
    if(!this->camera.isOpened()) {
        qDebug() << "camera opened fail";
        return false;
    }
    qDebug() << "camera opened success";
    return true;
}

void opencv_camera::closeCameraDev(void)
{
    this->camera.release();
}

QImage* opencv_camera::getCheckFaceImg(void)
{
    return &this->imgCheckFace;
}

QImage opencv_camera::getCameraImage(void)
{
    Mat frame;

    if(!this->camera.isOpened()) {
        qDebug() << "camera is not opened";
        return QImage();
    }
    if(!this->camera.grab())
    {
        qDebug() << "grab in opencv failed";
        return QImage();
    }
    this->camera >> frame;
    return MatToQImage(frame);
}

// Mat转图像
QImage opencv_camera::MatToQImage(const cv::Mat& mat)
{
    // 8-bits unsigned, NO. OF CHANNELS = 1
    if (mat.type() == CV_8UC1)
    {
        QImage image(mat.cols, mat.rows, QImage::Format_Indexed8);
        // Set the color table (used to translate colour indexes to qRgb values)
        image.setColorCount(256);
        for (int i = 0; i < 256; i++)
        {
            image.setColor(i, qRgb(i, i, i));
        }
        // Copy input Mat
        uchar *pSrc = mat.data;
        for (int row = 0; row < mat.rows; row++)
        {
            uchar *pDest = image.scanLine(row);
            memcpy(pDest, pSrc, mat.cols);
            pSrc += mat.step;
        }
        return image;
    }
    // 8-bits unsigned, NO. OF CHANNELS = 3
    else if (mat.type() == CV_8UC3)
    {
        // Copy input Mat
        const uchar *pSrc = (const uchar*)mat.data;
        // Create QImage with same dimensions as input Mat
        QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_RGB888);
        return image.rgbSwapped();
    }
    else if (mat.type() == CV_8UC4)
    {
        // Copy input Mat
        const uchar *pSrc = (const uchar*)mat.data;
        // Create QImage with same dimensions as input Mat
        QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32);
        return image.copy();
    }
    else
    {
        return QImage();
    }
}

void opencv_camera::setFaceRecognizer(QString path, int mode)
{
    if(mode == 0) myModel = cv::face::EigenFaceRecognizer::create();
    else if(mode == 1) myModel = cv::face::FisherFaceRecognizer::create();
    else if(mode == 2) myModel = cv::face::LBPHFaceRecognizer::create();
    else {
        myModel.reset();
        return ;
    }
    myModel->read(path.toStdString());
}

QImage opencv_camera::getCameraImage_face(void)
{
    Mat frame, gray;
    int ret = 0;

    if(!this->camera.isOpened()) {
        qDebug() << "camera is not opened";
        return QImage();
    }
    if(!this->camera.grab())
    {
        qDebug() << "grab in opencv failed";
        return QImage();
    }
    this->camera >> frame;

    std::vector<Rect>faces(0); // 存放人脸的向量容器
    CascadeClassifier faceCascade; // 分类器
    faceCascade.load("haarcascade_frontalface_alt2.xml"); // 加载分类器模型
    cvtColor(frame, gray, COLOR_RGB2GRAY); // 灰度化处理
    equalizeHist(gray, gray); // 变换后的图像进行直方图均值化处理
    faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, Size(92, 112)); // 以加载模型为准进行检测
    flip(frame, frame, 1); // 水平翻转
    for(int i = 0; i < faces.size(); i++) {
        Scalar color = Scalar(0, 255, 0); // 所取的颜色
        rectangle(frame, Point(frame.cols - faces[i].x - faces[i].width, faces[i].y), Point(frame.cols - faces[i].x, faces[i].y + faces[i].height), color, 1, 8); // 框出检测目标

        if(myModel.empty()) continue;
        Mat faceROI = gray(faces[i]);
        Mat faceROI2;

        if(faceROI.cols >= 92 && faceROI.rows >= 112) {
            resize(faceROI, faceROI2, Size(92, 112));
        }
        if(!faceROI2.empty()) {
            ret = myModel->predict(faceROI2);
            putText(frame, QString("%1").arg(ret).toStdString(), Point(frame.cols - faces[i].x - faces[i].width, faces[i].y - 2), FONT_HERSHEY_DUPLEX, 1, Scalar(0, 255, 0), 1, 8); // 添加文字
//            qDebug() << "predict code:" << ret;
        }
    }

    return MatToQImage(frame);
}
// 检测图片内是否有人脸
int opencv_camera::checkPicture_face(QString path)
{
    if(path.isNull()) return 65535;
    Mat picture, gray;
    picture = cv::imread(path.toStdString());

    std::vector<Rect>faces(0); // 存放人脸的向量容器
    CascadeClassifier faceCascade; // 分类器
    faceCascade.load("haarcascade_frontalface_alt2.xml"); // 加载分类器模型
    cvtColor(picture, gray, COLOR_RGB2GRAY); // 灰度化处理
    equalizeHist(gray, gray); // 变换后的图像进行直方图均值化处理
    faceCascade.detectMultiScale(gray, faces); // 以加载模型为准进行检测
    // 无人脸
    if(faces.size() < 1) {
        putText(picture, "No target", Point(0,picture.size().height / 2), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255), 1, 8); // 添加文字
    } else {
        for(int i = 0; i < faces.size(); i++) {
            rectangle(picture, Point(faces[i].x, faces[i].y), Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height), Scalar(0,0,255), 2, 8); // 框处检测目标
        }
    }
//    qDebug() << f0aces[0].x << faces[0].y << faces[0].width << faces[0].height;
//    qDebug() << "faces size is:" << faces.size();
    this->imgCheckFace = MatToQImage(picture);

    return faces.size();
}
// 保存一帧摄像头图片
bool opencv_camera::saveCameraImage(QString path)
{
    Mat frame;

    if(!this->camera.isOpened()) {
        qDebug() << "camera is not opened";
        return false;
    }
    if(!this->camera.grab())
    {
        qDebug() << "grab in opencv failed";
        return false;
    }
    this->camera >> frame;
    imwrite(path.toStdString(), frame);

    return true;
}
// 将图片预处理为92*112大小的标准图
bool opencv_camera::resizeStdPicture(QString readPath, QString savePath)
{
    if(readPath.isNull() || savePath.isNull()) return false;
    Mat picture, gray;
    picture = cv::imread(readPath.toStdString());

    std::vector<Rect>faces(0); // 存放人脸的向量容器
    CascadeClassifier faceCascade; // 分类器
    faceCascade.load("haarcascade_frontalface_alt2.xml"); // 加载分类器模型
    cvtColor(picture, gray, COLOR_RGB2GRAY); // 灰度化处理
    equalizeHist(gray, gray); // 变换后的图像进行直方图均值化处理
    faceCascade.detectMultiScale(gray, faces); // 以加载模型为准进行检测
    for(int i = 0; i < faces.size(); i++) {
        Mat faceROI = gray(faces[i]); // 取出包含人脸的矩阵
        Mat faceROI2;
//        qDebug() << faceROI.cols << faceROI.rows;
        // 尺寸要大于等于标准尺寸
        if(faceROI.cols >= 92 && faceROI.rows >= 112) {
            resize(faceROI, faceROI2, Size(92, 112));
            imwrite(savePath.toStdString(), faceROI2);
        }
    }

    return true;
}
// 获取图片的尺寸
QSize opencv_camera::getPictureSize(QString path)
{
    Mat picture;
    QSize size;

    picture = imread(path.toStdString());
    size.setWidth(picture.cols);
    size.setHeight(picture.rows);

    return size;
}

//Mat opencv_camera::norm_0_255(InputArray _src)
//{
//    Mat src = _src.getMat();
//    // 创建和返回一个归一化后的图像矩阵:
//    Mat dst;

//    switch (src.channels()) {
//    case 1:
//        cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
//        break;
//    case 3:
//        cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
//        break;
//    default:
//        src.copyTo(dst);
//        break;
//    }

//    return dst;
//}

bool opencv_camera::read_csv(const std::string& filename, std::vector<Mat>& images, std::vector<int>& labels, char separator)
{
    std::ifstream file(filename.c_str(), std::ifstream::in);
    if (!file) {
        qDebug() << "csv file open failure";
        return false;
    }
    std::string line, path, classlabel;
    while (getline(file, line)) //从文本文件中读取一行字符，未指定限定符默认限定符为“/n”
    {
        std::stringstream liness(line);//这里采用stringstream主要作用是做字符串的分割
        getline(liness, path, separator);//读入图片文件路径以分好作为限定符
        getline(liness, classlabel);//读入图片标签，默认限定符
        if (!path.empty() && !classlabel.empty()) //如果读取成功，则将图片和对应标签压入对应容器中
        {
            images.push_back(imread(path, 0));
            labels.push_back(atoi(classlabel.c_str()));
        }
    }

    return true;
}

bool opencv_camera::createRecognizerXMLFile(QString csvPath, QString savePath, QString fileName, int mode)
{
    std::vector<Mat> images;
    std::vector<int> labels;

    // 读取CSV出错则返回
    try {
        read_csv(csvPath.toStdString(), images, labels);
    } catch (cv::Exception& e) {
        qWarning() << "create recognizer failure, reason:" << e.msg.c_str();
        return false;
    }
    // 图片数量太少则返回
    if(images.size() < 1) {
        qWarning() << "create recognizer failure, images num too little";
        return false;
    }
    // 图片尺寸不对则返回
    for(int i = 0; i < images.size(); i++) {
        if(QSize(images[i].cols, images[i].rows) != QSize(92, 112)) {
            qWarning() << "create recognizer failure, images size is not std size";
            return false;
        }
    }

    std::string path = savePath.append("%1.xml").arg(fileName).toStdString();

    if(mode == 0) {
        Ptr<cv::face::EigenFaceRecognizer> model = cv::face::EigenFaceRecognizer::create();
        model->train(images, labels);
        model->save(path);
    } else if(mode == 1) {
        Ptr<cv::face::FisherFaceRecognizer> model1 = cv::face::FisherFaceRecognizer::create();
        model1->train(images, labels);
        model1->save(path);
    } else if(mode == 2) {
        Ptr<cv::face::LBPHFaceRecognizer> model2 = cv::face::LBPHFaceRecognizer::create();
        model2->train(images, labels);
        model2->save(path);
    }

    return true;
}


